# -*-coding: utf-8 -*-
"""
作者：TianyiFan
日期：2023-年06-月17日
用途：pora_generation
"""
import math
import random as rd
import numpy as np
import pandas as pd
from collections import OrderedDict
from common_fuc import *
# 统计当前项目代码行数
# git ls-files | xargs cat | wc -l
# # 细分每个文件的代码行数，相当于把上面命令细化：
# git ls-files | xargs wc -l

# ---------------------------------全局迭代数组+变量定义/算法调参+问题规模定义------------------------------------------------------
total_plan = 10    # 全局属性，不会更改，总迭代次数
iter_count = 0     # 全局属性，直接修改，随着PPmodel的迭代，次数增加
search_DS = 10     # 单次迭代队列的深度
step_DS = 2        # 滚动的求解固定的步长
rolling_number = 5 # CSNS的循环次数
add_cost = False   # 是否加上堆场占用成本
max_bound = 99999999
outflag_RMP = 0
outflag_PP = 0
outflag_SWO = 0
single_time_limit = 20
test_running_limitation = 600
problem_define = {
    "ISG0": {"V": 12, "B": 2, "num_row_block": 3, "num_column_block": 2},
    "ISG1": {"V": 15, "B": 2, "num_row_block": 3, "num_column_block": 2},
    "ISG2": {"V": 20, "B": 3, "num_row_block": 2, "num_column_block": 4},
    "ISG3": {"V": 45, "B": 6, "num_row_block": 3, "num_column_block": 6},
    "ISG4": {"V": 60, "B": 8, "num_row_block": 3, "num_column_block": 8},
    "ISG5": {"V": 75, "B": 10, "num_row_block": 3, "num_column_block": 10},
    "ISG6": {"V": 100, "B": 14, "num_row_block": 4, "num_column_block": 9},
}

# 参数可视化类
class data(object):
    def __init__(self, button=False):
        column_name = ['vessel_idx', 'berth_idx', "c_ui","c_li",'r_0', 'r_2', 'handle_list',
                       'num_container',"conflict_list","seq_value","no_conflict_list"]
        vessel_index = [v for v in range(1, V + 1)]
        berth_idx = [berth_i[i] for i in range(1, V + 1)]
        unloading_num = [c_ui[i] for i in range(1,V+1)]
        loading_num = [c_li[i] for i in range(1, V + 1)]
        num_container = [c_li[i]+c_ui[i] for i in range(1,V+1)]
        r_0 = [r_ih[i][0] for i in range(1, V + 1)]
        r_1 = [r_ih[i][1] for i in range(1, V + 1)]
        handle_time = [[t for t in range(T) if l_it[i][t] == 1] for i in range(1, V + 1)]
        # 10. 识别和每艘船可能存在服务冲突的轮船集合和不存在工作冲突的集合
        conflict_list = {i:[] for i in range(1,V+1)}
        no_conflict_list = {i: [] for i in range(1, V + 1)}
        seq_value = {i:[] for i in range(1,V+1)}
        total_vessel = set(i for i in range(1,V+1))
        for i in range(1, V + 1):
            time_set1 = set(handle_time[i-1])
            for j in range(1, V + 1):
                if i != j:
                    time_set2 =  set(handle_time[j-1])
                    if len(time_set1 & time_set2) > 0:
                        conflict_list[i].append(j)
            temp_conflict = set(conflict_list[i])
            temp_no_conflict = total_vessel-temp_conflict
            no_conflict_list[i]=list(temp_no_conflict)
            no_conflict_list[i].remove(i)
        for i in range(1,V+1):
            seq_value[i] = len(conflict_list[i])
        column_list = {column_name[0]: vessel_index,
                       column_name[1]: berth_idx,
                       column_name[2]: unloading_num,
                       column_name[3]: loading_num,
                       column_name[4]: r_0,
                       column_name[5]: r_1,
                       column_name[6]: handle_time,
                       column_name[7]: num_container,
                       column_name[8]:conflict_list.values(),
                       column_name[9]:seq_value.values(),
                       column_name[10]:no_conflict_list.values()}
        self.data = pd.DataFrame(column_list)
        if button:
            print(self.data)

# ---------------------------------问题规模定义---------------------------------------------------------------------
'''
算例规模：
ISG1: 2*3 布局  K=60 V=15 B=2  
'''
seed = 19
rd.seed(seed)
num_row_block, num_column_block = 3, 6  # X排，Y列  堆场总数=排*列*5*2
V: int = 45  # index of vessel
B: int = 10   # index of berth
H: int = 2   # index of floor
T: int = 42  # index of time_step
G0: int = num_row_block * num_column_block  # index of block of one floor
G: int = num_row_block * num_column_block * 2  # index of Block of two floor
K0: int = num_column_block * num_row_block * 5  # index of Subblock 有H层，因此索引*2
K1: int = num_column_block * num_row_block * 5 * H  # index of Subblock 有H层 K总数
pair_N = K1  # 相邻subblock--以subblock为单位
pair_new = K1 # subblock---不重复的数量长度
A0: int = num_column_block  # 一层的车道集合
A: int = (num_column_block + 1) * 2  # 两层的车道集合，总数=堆场侧列数+1的两倍，考虑了左侧和右侧的车道
max_lane = rd.randint(V // 2, V - 1)

dong = num_row_block // 2 * num_column_block
set_vessel = np.ones(V+2,dtype=int)  #　初始化所有船的计划集合长度为1，后面更新过程中，动态改变计划集合的长度
'''
船的索引，[1-V+1)
堆场的索引， 第一层： [0,K0);  第二层：[K0,K1)
block的索引，第一层： [0,G0);  第二层：[G0,G)
pair_对索引，(0,pair_N)  ---后续进行了修改
'''
# ---------------------------------问题参数定义【不随规模改变的参数】---------------------------------------------------------------------
container_maximum, container_minimum = 400, 100  # 集装箱的最大最小区间
subblock_length, lane_length, basic_length, twoblock_len = 50, 30, 50, 35  # 堆场的长宽
z = 240  # 每个subblock的最大容量
bigM = z
t_subblock = 6048
C = dong * 30 * 4
capacity = 120  # 单个小车单个timestep（4H）处理30TEU--处理效率：2分钟一个一个TEU
Minimum_block = 0  # 最少的堆场数量
time_u, time_p, = 60, 120  # 岸桥侧同时装卸集装箱的单位操作时间,堆场侧异层转运单个集装箱的单位操作时间, 60/120s
time_trans = 10  # AGV 以10m/s的速度运输箱子
vessel_container = [[60, 150], [180, 450], [450, 600]]  # 不同类型的船，预期unloadin/loading集装箱数量
vessel_handling = [[2, 4], [3, 5], [4, 6]]  # 不同类型的船，预期handling的时间
"""
  泊位分布： 250米为一个泊位，每个泊位之间间隔30米
  首个泊位的索引：0
  首个泊位的x坐标：（30+125，0）
  后面泊位的递推坐标：berth[i+1]=berth[i-1]+280
  """
# ---------------------------------数组定义---------------------------------------------------------------------
c_ui = np.zeros(V + 2, dtype=int)  # 轮船i的unloading数量
c_li = np.zeros(V + 2, dtype=int)  # 轮船i的loading数量
total_i = np.zeros(V + 2, dtype=int)  # 转运到集装箱的数量
total_s = np.zeros(V + 2, dtype=int)  # 集装箱数量需要总数
r_ih = np.zeros([V + 2, H], dtype=int)  # 第h层预留的集装箱数量
l_it = np.zeros([V + 2, T], dtype=int)  # 第t个time step是否有装卸任务
w_ika = np.zeros([V + 2, K1, A], dtype=int)  # 从subblock k 到轮船 i 的泊位经过lane a 为1，否则为0
W_a = np.zeros(A, dtype=int)  # 第a个lane在一个time step内能够经过的车辆
berth_i = np.zeros(V + 2, dtype=int)    # 第i艘船停泊的泊位
t_lki = np.zeros([K1, V + 2], dtype=float)  # 从第k个subblock 装载到轮船i的位置的单位运输时间
t_uki = np.zeros([K1, V + 2], dtype=float)  # 从轮船i卸载到第k个subblock 的单位运输时间
handle_i = np.zeros(V + 2, dtype=int)
start_i = np.zeros(V + 2, dtype=int)
total_handle = np.zeros(T, dtype=int)
berth_location = np.zeros([V + 2, 2], dtype=int)
lane_location = np.zeros([A, 2], dtype=int)
subblock_location = np.zeros([K1, 2], dtype=int)
conflict_t =  {}
subblock_info = []
block_set = []
neighbor_set = []        # 基于每个subblock的neighbor_pair  [1,2,3] --gurobi模型中使用
neighbor_pair = []       # 每个pair对，存在一定重复         [1,2],[1,3]---pp问题中拆分使用
sorted_unique_pairs = [] # 每个pair对，不存在重复的
neighbor_sublock_pair = {}  # 存放每个subblock有关的neighbor_pair_index
test_data = data()          # 轮船的相关数据


# 计算loading过程，从subblock到轮船泊位的unloading distance
def cal_loading_distance(subblock_index, row_index, column_index, berth_index) -> int:
    if subblock_index < K0:
        passing_lane_index = column_index + 1
    else:
        passing_lane_index = column_index + 2

    # 第一行到泊位的距离： 垂直移动距离=纵坐标距离; 水平移动距离=到block右侧车道的距离;; 泊位侧移动距离=车道lane到泊位的距离
    if row_index == 0 or row_index == num_row_block:
        column_length = subblock_location[subblock_index][1]
        if subblock_index % 10 >= 5:  # 堆场索引个位数>=5
            row_length = (10 - subblock_index % 10) * 50 + 15
        else:                         # 堆场索引个位数<=5
            row_length = (5 - subblock_index % 10) * 50 + 15
        base_length = np.abs(lane_location[passing_lane_index, 0] - berth_location[berth_index, 0])
    # 最后一行的距离：垂直移动距离=最下方到海岸线的直线距离；水平移动距离=0；泊位侧移动距离=堆场横坐标和泊位中心坐标之差
    elif row_index == num_row_block - 1 or row_index == num_row_block*2 -1:
        column_length = 50
        row_length = 0
        a = subblock_location[subblock_index, 0]
        b = berth_location[berth_index, 0]
        base_length = np.abs(a - b)
    # 中间行到泊位的距离
    else:
        column_length = subblock_location[subblock_index][1]
        if subblock_index % 10 >= 5:
            row_length = (10 - subblock_index % 10) * 50 + 15
        else:
            row_length = (5 - subblock_index % 10) * 50 + 15
        base_length = np.abs(lane_location[passing_lane_index, 0] - berth_location[berth_index, 0])
    # if subblock_index in [67]:
    #     print(
    #         "loading-sublock:{0} to berth-{4}  base_leng={1}  column_leng={2}  row_len={3}  row_index={5}  location=[{6},{7}]  berth_x ={8}"
    #             .format(subblock_index, base_length, column_length, row_length, berth_index,
    #                     row_index, subblock_location[subblock_index, 0], subblock_location[subblock_index, 1], berth_location[berth_index, 0]))
    totaldistance = base_length + column_length + row_length
    return totaldistance


# 计算unloading过程，从vessel到subblock的unloading distance
def cal_unloading_distance(subblock_index, row_index, column_index, berth_index) -> int:
    if subblock_index < K0:
        passing_lane_index = column_index   # 堆场左侧的车道
    else:
        passing_lane_index = column_index + 1
    # 第一行到泊位的距离： 垂直移动距离=纵坐标距离; 水平移动距离=到block左侧车道的距离; 泊位侧移动距离=车道lane到泊位的距离
    if row_index == 0 or row_index == num_row_block:  # 每一层的首行
        column_length = (num_row_block / 2) * 35 + (num_row_block / 2 - 1) * 30 + basic_length
        if subblock_index % 10 >= 5:
            row_length = (subblock_index % 10 - 5) * 50 + 15
        else:
            row_length = (subblock_index % 10) * 50 + 15
        base_length = np.abs(lane_location[passing_lane_index, 0] - berth_location[berth_index, 0])
    # 最后一行到泊位的距离： 垂直移动距离=纵坐标距离; 水平移动距离=0; 泊位侧移动距离=车道lane到泊位的距离
    elif row_index == num_row_block - 1 or row_index == (num_row_block * 2 - 1):  # 尾行
        row_length = 0
        column_length = 50
        a = subblock_location[subblock_index, 0]
        b = berth_location[berth_index, 0]
        base_length = np.abs(a - b)
    else:
        if row_index % 2 != 0:  # 奇数行-堆场下方
            column_length = ((num_row_block - row_index) % 2) * 35 + (
                    ((num_row_block - row_index) % 2) - 1) * 30 + basic_length
        else:  # 偶数行-堆场上方
            column_length = ((num_row_block - (row_index - 1)) % 2) * 35 + (
                    ((num_row_block - 1 - row_index) % 2) - 1) * 30 + basic_length
        if subblock_index % 10 >= 5:
            row_length = (subblock_index % 10 - 5) * 50 + 15
        else:
            row_length = (subblock_index % 10) * 50 + 15
        base_length = np.abs(lane_location[passing_lane_index, 0] - berth_location[berth_index, 0])
    #
    # if subblock_index in [67]:
    #     print(
    #         "unloading-sublock:{0} to berth-{4}  base_leng={1}  column_leng={2}  row_len={3}  row_index={5}  location=[{6},{7}]  berth_x ={8}"
    #             .format(subblock_index, base_length,column_length, row_length, berth_index,
    #                     row_index,subblock_location[subblock_index, 0],subblock_location[subblock_index, 1],berth_location[berth_index,0]))
    totaldistance = base_length + column_length + row_length
    return totaldistance


# 初始化所有的参数
def initialization() -> None:
    global Minimum_block  # 全局定义的需要变动的参数，需要global声明
    # 1. vessel i转运到j的集装箱数量--修改 done--要分轮船的类型
    vessel_type = 0
    for i in range(1, V + 1):
        c_ui[i] = rd.randint(vessel_container[vessel_type][0], vessel_container[vessel_type][1])
        c_li[i] = rd.randint(vessel_container[vessel_type][0], vessel_container[vessel_type][1])
        handle_i[i] = rd.randint(vessel_handling[vessel_type][0], vessel_handling[vessel_type][1])
        # print("vessel-{0} 的handle_i={1}  vessel-type={2}".format(i, handle_i[i],vessel_type))
        if vessel_type == 2:
            vessel_type = 0
        else:
            vessel_type += 1
    # 10. 根据轮船集装箱数量，生成handling time
    for i in range(1, V + 1):
        start_i[i] = rd.randint(0, T - handle_i[i] - 1)
        # print("vessel-{0} 的开始handling time={1}".format(i, start_i[i]))
        for t in range(start_i[i], start_i[i] + handle_i[i]):
            l_it[i][t] = 1
            total_handle[t] += 1

    # 2. 计算一艘船需要的container总数以及每艘船需要的subblock数量--修改done
    for i in range(1, V + 1):
        total_i[i] += c_li[i] + c_ui[i]
        total_s[i] = math.ceil(total_i[i] / z)
        if i != 0 and i != V + 1:
            Minimum_block = total_s[i] + Minimum_block
    # print("函数中的Minimum_block={0}".format(Minimum_block))

    # 3. 随机生成每一层的 subblock 数量
    for i in range(1, V + 1):
        r_ih[i][0] = rd.randint(1, max(total_s[i] - 2,2))  #　最少1个，最多总数-2个
        # r_ih[i][0] = total_s[i] * 3
        r_ih[i][1] = max(total_s[i] - r_ih[i,0], 1)           #  取总数-上层数量
        # r_ih[i][1] = total_s[i] * 3

    # 4.生成泊位的坐标，并分配泊位给vessel i
    for g_index in range(B + 1):
        g = g_index % num_column_block
        if g == 0:
            berth_location[g_index][0] = 155
        else:
            berth_location[g_index][0] = berth_location[g_index- 1][0] + 250 + lane_length
        berth_location[g][1] = 0  # 泊位的纵坐标
    for i in range(1, V + 1):
        berth_i[i] = (i - 1) % B

    # 5.生成车道的坐标--用于判断baseline距离
    for r in range(0, A):
        if r <= num_column_block:
            if r == 0:
                lane_location[r, 0] = 15
                lane_location[r, 1] = 0
            else:
                lane_location[r, 0] = lane_location[r - 1, 0] + subblock_length * 5 + lane_length
        else:
            if r - num_column_block - 1 == 0:
                lane_location[r, 0] = 15
                lane_location[r, 1] = 0
            else:
                lane_location[r, 0] = lane_location[r - 1, 0] + subblock_length * 5 + lane_length

    # 6.生成第一层的subblock的坐标, 第一层的subblock 索引为(0-K0]
    subblock_location[0, 0] = 30  # 左上角的subblock的横坐标
    subblock_location[0, 1] = basic_length + (num_row_block / 2 * twoblock_len) + (num_row_block / 2 - 1) * lane_length
    subblock_location[K0][0] = 30  # 左上角的subblock的横坐标
    subblock_location[K0][1] = basic_length + (num_row_block / 2 * twoblock_len) + (num_row_block / 2 - 1) * lane_length
    # 第1排的堆场坐标
    for i in range(1, num_column_block * 5):
        if i % 5 == 0:
            subblock_location[i, 0] = subblock_location[i - 1, 0] + subblock_length + lane_length  # 横坐标+80
            subblock_location[i, 1] = subblock_location[i - 1, 1]  # 纵坐标相同
        else:
            subblock_location[i, 0] = subblock_location[i - 1, 0] + subblock_length
            subblock_location[i, 1] = subblock_location[i - 1, 1]
    # 第1排往后subblock的坐标：基于第一排的subblock递推
    for i in range(1, num_row_block):
        kaitoublock = i * num_column_block * 5
        for j in range(kaitoublock, kaitoublock + num_column_block * 5):
            if j % (num_column_block * 5) == 0:  # 每行block头和上行block头的关系
                if i % 2 == 0:  # 偶数行
                    subblock_location[j, 0] = lane_length
                    subblock_location[j, 1] = subblock_location[(i - 1) * num_column_block * 5, 1] - lane_length
                else:  # 奇数行
                    subblock_location[j, 0] = lane_length
                    subblock_location[j, 1] = subblock_location[(i - 1) * num_column_block * 5, 1] - twoblock_len
            elif j % 5 == 0:  # 前block尾和后block头的递推关系：横坐标+30，纵坐标相同
                subblock_location[j, 0] = subblock_location[j - 1, 0] + lane_length + subblock_length
                subblock_location[j, 1] = subblock_location[j - 1, 1]
            else:  # block中subblock之间的递推关系：横坐标+50；纵坐标相同
                subblock_location[j, 0] = subblock_location[j - 1, 0] + subblock_length
                subblock_location[j, 1] = subblock_location[j - 1, 1]

    # 7.生成第二层的subblock的坐标, 第二层为[K0,K1)
    subblock_location[K0, 0] = 30  # 左上角的subblock的横坐标
    subblock_location[K0, 1] = basic_length + (num_row_block / 2 * twoblock_len) + (num_row_block / 2 - 1) * lane_length
    for i in range(K0 + 1, K0 + num_column_block * 5):  # 第二层第一排
        if i % 5 == 0:
            subblock_location[i, 0] = subblock_location[i - 1, 0] + subblock_length + lane_length  # 横坐标+80
            subblock_location[i, 1] = subblock_location[i - 1, 1]  # 纵坐标相同
        else:
            subblock_location[i, 0] = subblock_location[i - 1, 0] + subblock_length
            subblock_location[i, 1] = subblock_location[i - 1, 1]
    # 第1排往后subblock的坐标：基于第一排的subblock递推
    for i in range(num_row_block + 1, num_row_block * 2):
        kaitoublock = i * num_column_block * 5
        for j in range(kaitoublock, kaitoublock + num_column_block * 5):
            if j % (num_column_block * 5) == 0:  # 每行block头和上行block头的关系
                if i % 2 == 0:  # 偶数行
                    subblock_location[j, 0] = lane_length
                    subblock_location[j, 1] = subblock_location[(i - 1) * num_column_block * 5, 1] - lane_length
                else:  # 奇数行
                    subblock_location[j, 0] = lane_length
                    subblock_location[j, 1] = subblock_location[(i - 1) * num_column_block * 5, 1] - twoblock_len
            elif j % 5 == 0:  # 前block尾和后block头的递推关系：横坐标+30，纵坐标相同
                subblock_location[j, 0] = subblock_location[j - 1, 0] + lane_length + subblock_length
                subblock_location[j, 1] = subblock_location[j - 1, 1]
            else:  # block中subblock之间的递推关系：横坐标+50；纵坐标相同
                subblock_location[j, 0] = subblock_location[j - 1, 0] + subblock_length
                subblock_location[j, 1] = subblock_location[j - 1, 1]

    # 8.封装每个subblock的信息：subblock的编号，subblock所在的block，所在列的编号，所在行编号，loading的车道编号
    for k in range(0, K1):
        mid_list = np.zeros(B, dtype=int)
        mid_list2 = np.zeros(B, dtype=int)
        if k < K0:  # 第一层的subblock
            unit_k = Subblock_class(k, k // 5,
                                    (k % (num_column_block * 5)) // 5,
                                    k // (5 * num_column_block),
                                    (k % (num_column_block * 5)) // 5,
                                    mid_list, mid_list2)
        else:  # 第二层的subblock
            unit_k = Subblock_class(k, k // 5,
                                    ((k - K0) % (num_column_block * 5)) // 5 + num_column_block,
                                    (k - K0) // (5 * num_column_block) + num_row_block,
                                    ((k - K0) % (num_column_block * 5)) // 5 + num_column_block + 1,
                                    mid_list, mid_list2)

        for b in range(B):
            unit_k.distance_berth[b] = cal_loading_distance(unit_k.index, unit_k.rowbelong,
                                                    unit_k.columnbelong, b)
            unit_k.unloading_distance[b] = cal_unloading_distance(unit_k.index, unit_k.rowbelong,
                                                            unit_k.columnbelong, b)
            # if unit_k.index in [18, 19]:
            #     print("unloading—distance计算完毕！ subblock-{0} 到 泊位：{1} 的loading距离={2} unloading距离={3}".
            #           format(unit_k.index,b,unit_k.distance_berth[b],unit_k.unloading_distance[b]))
        # 所有信息封装到一个对象后，再丢到列表中
        subblock_info.append(unit_k)

    # 9. 生成unloading过程泊位到堆场的距离以及loading过程中堆场到泊位的距离
    for k in range(K1):
        for i in range(1, V + 1):
            t_lki[k][i] = subblock_info[k].distance_berth[berth_i[i]] / time_trans
            t_uki[k][i] = subblock_info[k].unloading_distance[berth_i[i]] / time_trans

    # 11. 生成车道的最大限制与w_ika
    # for r in range(A):
    #     W_a[r] = rd.randint(max_lane - 5, max_lane)
    # for i in range(1, V + 1):
    #     for k in range(K1):
    #         lane_index = subblock_info[k].passinglane
    #         for r in range(A):
    #             w_ika[i][k][r] = 1 if lane_index == r else 0
    global test_data
    test_data = data()

    # 11. 生成每个t内存在作业拥挤的vessel列表
    for t in range(T):
        conflict_t[t] = []
    handle_list = list(test_data.data['handle_list'])
    for i in range(V):
        for work_time in handle_list[i]:
            conflict_t[work_time].append(i+1)

def set_generation() -> None:
    '''
    根据已生成的参数初始化neighbor_block和neigbor_pair
    :return:
    '''
    # 1.将相同block的subblock放到一个block中
    for g in range(G):
        mid_list = []
        for i in range(5):
            mid_list.append(g * 5 + i)
        block_set.append(mid_list)

    # 2. 将邻近的 subblock 放到一个某个subblock的列表中
    for i in range(0, num_row_block * 2):
        start_index = i * num_column_block * 5
        end_index = start_index + num_column_block * 5  # [start_index,endindex) 左闭右开
        for j in range(start_index, end_index):
            mid_list = [j]  # 先把自己放进去
            # 处理第一层/第二层首行的subblock
            if i == 0 or i == num_row_block:
                if j == start_index:  # 最左边
                    mid_list.append(j + 1)
                elif j == end_index - 1:  # 最右边
                    mid_list.append(j - 1)
                else:  # 中间
                    if subblock_location[j, 0] - subblock_location[j - 1, 0] == 50:
                        mid_list.append(j - 1)
                    if subblock_location[j + 1, 0] - subblock_location[j, 0] == 50:
                        mid_list.append(j + 1)
            # 处理第一层/第二层尾行的neighbor
            elif i == num_row_block - 1 or i == num_row_block * 2 - 1:
                #  如果num_row_block 总数为偶数，意味着最后一行，只需要管两侧的neighbor pair
                if num_row_block % 2 ==0:
                    if j == start_index:      # 最左边的subblock
                        mid_list.append(j + 1)
                    elif j == end_index - 1:  # 最右边的subblock
                        mid_list.append(j - 1)
                    else:  # 中间的subblock，右边+左边
                        if subblock_location[j, 0] - subblock_location[j - 1, 0] == 50:
                            mid_list.append(j - 1)
                        if subblock_location[j + 1, 0] - subblock_location[j, 0] == 50:
                            mid_list.append(j + 1)
                # 　如果num_row_block 总数为奇数，意味着最后一行，单独成排，需要加上前一排相同位置
                else:
                    if j == start_index:   # 最左边的subblock
                        mid_list.append(j + 1)
                    elif j == end_index - 1:  # 最右边的subblock
                        mid_list.append(j - 1)
                    else:  # 中间的subblock，右边+左边
                        if subblock_location[j, 0] - subblock_location[j - 1, 0] == 50:
                            mid_list.append(j - 1)
                        if subblock_location[j + 1, 0] - subblock_location[j, 0] == 50:
                            mid_list.append(j + 1)
                    mid_list.append(j - 5 * num_column_block)  # 上方的subblock
            # 处理中间行的neighbor
            else:
                # 如果总行数为奇数：
                # 第一层的堆场，中间行按照奇数行：和下方成Pair, 偶数行和上方成pair，
                # 第二层的堆场，中间行按照奇数行：和上方成pair，偶数行和下方成pari
                # 如果总行数为偶数：
                # 奇数行均鸡和下方成Pair, 偶数行均和上方成pair
                if i % 2 == 0:  # 偶数行：左+右+上
                    if j == start_index:      # 最左边
                        mid_list.append(j + 1)
                    elif j == end_index - 1:  # 最右边
                        mid_list.append(j - 1)
                    else:
                        if subblock_location[j, 0] - subblock_location[j - 1, 0] == 50:  # 左边的block
                            mid_list.append(j - 1)
                        if subblock_location[j + 1, 0] - subblock_location[j, 0] == 50:  # 右边的block
                            mid_list.append(j + 1)
                    if num_row_block%2==0:
                        mid_list.append(j - 5 * num_column_block)  # 上方的subblock
                    else:
                        if i<num_row_block:
                            mid_list.append(j - 5 * num_column_block)  # 上方的subblock
                        else:
                            mid_list.append(j + 5 * num_column_block)  # 下方的subblock
                else:  # 奇数行：左+右+下
                    if j == start_index:  # 最左边
                        mid_list.append(j + 1)
                    elif j == end_index - 1:  # 最右边
                        mid_list.append(j - 1)
                    else:
                        if subblock_location[j, 0] - subblock_location[j - 1, 0] == 50:  # 左边的block
                            mid_list.append(j - 1)
                        if subblock_location[j + 1, 0] - subblock_location[j, 0] == 50:  # 右边的block
                            mid_list.append(j + 1)
                    if num_row_block%2==0:
                        mid_list.append(j + 5 * num_column_block)  # 上方的subblock
                    else:
                        if i < num_row_block:
                            mid_list.append(j + 5 * num_column_block)  # 第一层奇数行：下方的subblock
                        else:
                            mid_list.append(j - 5 * num_column_block)  # 第二层奇数行：下方的subblock
            neighbor_set.append(mid_list)
    for neibor_pool in neighbor_set:
        index = neibor_pool[0]
        for neibor_index in neibor_pool[1:]:
            neighbor_pair.append([index, neibor_index])
    global pair_N
    global pair_new
    global sorted_unique_pairs
    unique_pairs = list(set(tuple(sorted(pair)) for pair in neighbor_pair))
    sorted_unique_pairs = sorted(unique_pairs, key=lambda x: x[0])
    pair_new = len(sorted_unique_pairs)
    pair_N = len(neighbor_pair)
    # print("去重后：pair_N = {} \n{}".format(len(sorted_unique_pairs),sorted_unique_pairs))
    # print(neighbor_set)
    # 3.将不同的neighbor_pair 关联到对应的subblock上，方便进行索引
    for pair in range(pair_new):
        for index in sorted_unique_pairs[pair]:
            if pair not in subblock_info[index].pair_belong:
                subblock_info[index].pair_belong.append(pair)   # pair是堆场的编号，index是subblock的编号
    # for k in range(K0):
    #     print("subblock-{} 对应的pair编号：{} ".format(k,subblock_info[k].pair_belong))



def print_info() -> None:
    # region 参数输出验证
    print('-----------berth信息------------')
    for b in range(0, B):
        print("泊位:{0} 的坐标：({1:>4},{2})".format(b, berth_location[b, 0], berth_location[b, 1]))
    # print('-----------lane信息------------')
    # for r in range(0, A):
    #     if r < A // 2:
    #         print("第{0}层  车道-{1} ({2},{3})".format(0, r, lane_location[r, 0], lane_location[r, 1]))
    #     else:
    #         print("第{0}层  车道-{1} ({2},{3})".format(1, r, lane_location[r, 0], lane_location[r, 1]))
    # print('-----------subblock信息------------')
    # for k in range(0, K1):
    #     if k == 0:
    #         print("第一层的subblock信息-----------")
    #     if k == K0:
    #         print("第二层的subblock信息-----------")
    #     print("subblock:{0:<2}  blockbelong:{1:<2}  columnbelong={2}  "
    #           "rowbelong={3}  passinglane={4}"
    #           .format(subblock_info[k].index,
    #                   subblock_info[k].blockbelong,
    #                   subblock_info[k].columnbelong,
    #                   subblock_info[k].rowbelong,
    #                   subblock_info[k].passinglane))
    print('-----------vessel信息------------')
    num_firstblock = 0
    num_secondblock = 0
    for i in range(1, V + 1):
        # print("vessel-{0:>2}--泊位{6} loading={1:>4} unloading={2:>4}  total_container={3}  r_i1={4}  r_i2={5}  ".
        #       format(i, c_li[i], c_ui[i], total_i[i], r_ih[i][0], r_ih[i][1], berth_i[i]))
        for h in range(2):
            if h == 0:
                num_firstblock += r_ih[i][h]
            else:
                num_secondblock += r_ih[i][h]
    print_pd(test_data.data)
    print("最少subblock数={0}  第一层：{1}   第二层：{2}".format(Minimum_block, num_firstblock, num_secondblock))

    # print('-----------handling_table信息------------')
    # for i in range(1, V + 1):
    #     print("vessel-[{0:<2}]".format(i), end='')
    #     for t in range(T):
    #         print("{0:>2}".format(l_it[i][t]), end='')
    #     if i == V:
    #         print("  最大handling=%d" % max(total_handle))
    #     else:
    #         print()
    print("vessel-[00]", end='')
    for t in range(T):
        print("{0:>2}".format(total_handle[t]), end='')
    print()

    # print('-----------handling_time信息------------')
    # for i in range(1, V + 1):
    #     print("handle_{0} = {1} timestep".format(i, handle_i[i]))
    print('-----------block信息------------')
    for g in range(G):
        if g == 0:
            print('第一层的Block-------')
        if g == num_column_block * num_row_block:
            print('第二层的Block-------')
        for i in range(5):
            print("{0:<4}".format(block_set[g][i]), end='')
        if (g + 1) % num_column_block == 0:
            print("  |  ")
        else:
            print("  |  ", end='')
    # print('-----------距离与时间计算测试------------')
    # for k in range(K1):
    #     # if k in [15, 19] or k in [55, 59]:
    #     for g in range(0, B):
    #         print(
    #             "subblock-{0} 到泊位{1}的loading距离={2}  unloading距离={9} berth:({3},{4})  subblock=({5},{6})  t_lk={7}  t_uk={8}"
    #             .format(k, g, subblock_info[k].distance_berth[g],
    #                     berth_location[g][0], 0,
    #                     subblock_location[k][0], subblock_location[k][1],
    #                     t_lki[k][g + 1], t_uki[k][g + 1], subblock_info[k].unloading_distance[g]))
    # print('-----------neighbor_subblock信息------------')
    # for k in range(K1):
    #     print("subblock_{0:<2}的 neighbor 有:".format(k), end="  ")
    #     for j in range(len(neighbor_set[k])):
    #         print("{0:<3}".format(neighbor_set[k][j]), end="  ")
    #     print()
    # endregion


class gurobi_sol:  # 静态类，用于存放不同算法求解的结果
    alpha_uih = np.zeros([V + 2, H], dtype=int)
    alpha_lih = np.zeros([V + 2, H], dtype=int)
    beta_lik = np.zeros([V + 2, K1], dtype=int)
    beta_uik = np.zeros([V + 2, K1], dtype=int)
    ganma_ik = np.zeros([V + 2, K1], dtype=int)
    Fpi_l = np.zeros(V+2,dtype=float)
    Fpi_u = np.zeros(V+2,dtype=float)


class common_func:
    '''
    通用的静态方法，通过直接调用类即可执行的操作
    '''
    @staticmethod
    def update_plan_pool(base):
        '''
        根据记录gurobi的静态变量，更新输入的计划
        :param base:
        :return:
        '''
        # print(f"common_func 调用时，pair_N = {pair_N}")
        check_cost = 0
        for i in range(1,V+1):
            # 更新A_pi_k 、 B_pig
            for g in range(G):
                block_count = 0
                for k in block_set[g]:
                    if gurobi_sol.ganma_ik[i, k] > 0.5:
                        base.A_pi_k[i,0, k] = 1
                        block_count += 1
                base.B_pig[i,0, g] = block_count
            # 更新D_pin
            for pair in range(pair_N):
                pair_count = 0
                for k in neighbor_pair[pair]:
                    if gurobi_sol.ganma_ik[i, k] > 0.5:
                        # if i in [1,2,5]:
                        #     print("vessel - {} pair={} 预留subblock ={}".format(i,neighbor_pair[pair],k))
                        pair_count += 1
                base.D_pin[i,0,pair] = pair_count
            # 更新Fpi_l，Fpi_u
            # todo 先计算loading和unloadng差值和最大值，赋值后计算cost
            tao_loading = 0
            tao_unloading = 0
            for k in range(K0):
                tao_loading += gurobi_sol.beta_lik[i, k]
                tao_unloading += gurobi_sol.beta_uik[i, k]
            base.Fpi_l[i,0] = abs(gurobi_sol.alpha_lih[i, 0] - tao_loading)
            base.Fpi_u[i,0] = abs(gurobi_sol.alpha_uih[i, 0] - tao_unloading)
            # print("vessel-{}  base计算的Fpi_l = {}  gurobi模型结果直接得到Fpi_l={}".format(i,base.Fpi_l[0,i],para.gurobi_sol.alpha_lih[i,0]))
            # 更新Cost_pi
            delta_l = max(gurobi_sol.alpha_lih[i])
            delta_u = max(gurobi_sol.alpha_uih[i])
            cost1 = time_p * (delta_l + delta_u)
            # cost2 = time_u * (para.gurobi_sol.Fpi_u[i]+ para.gurobi_sol.Fpi_l[i])  # 直接用gurobi求解得到绝对值变量进行计算
            cost2 = time_u * (base.Fpi_l[i,0] + base.Fpi_u[i,0])                     # 直接用更新求解得到绝对值变量进行计算
            cost3 = 0
            for k in range(K1):
                cost3 += t_uki[k, i] * gurobi_sol.beta_uik[i, k] + t_lki[k, i] * gurobi_sol.beta_lik[i, k]
            base.Cost_pi[i,0] = cost1 + cost2 + cost3
            check_cost += base.Cost_pi[i,0]
            print(f"Gurobi 得到的初始解中-轮船-{i} 的Cost={base.Cost_pi[i,0]:.2f}")
        print('基于Gurobi初始解计划得到的cost={:.2f} h  转换为秒={:.2f}  '.format(check_cost/3600,check_cost))
        return base

# updata_plan_pool 维护了三套，分别根据初始解中的变量更新base、PP中的变量更新base，gurobi中的解更新base，其中后面两套更新的方法完全一致



def size_change(problem):
    '''
    修改问题规模
    :param problem:
    :return:
    '''
    global V,B,num_column_block,num_row_block,dong
    global G0,G,K0,K1,A0,A
    global c_ui,c_li
    global total_i,total_s
    global r_ih
    global w_ika, W_a
    global t_lki, t_uki
    global handle_i,total_handle,start_i,l_it
    global berth_location, subblock_location, lane_location,berth_i
    global set_vessel
    # 根据问题规模修改
    size_name = "ISG"+str(problem)
    V = problem_define[size_name]['V']
    B = problem_define[size_name]['B']
    num_row_block = problem_define[size_name]['num_row_block']
    num_column_block = problem_define[size_name]['num_column_block']
    dong = num_row_block // 2 * num_column_block


    # 重新定义问题规模的其他参数
    G0 = num_row_block * num_column_block  # index of block of one floor
    G = num_row_block * num_column_block * 2  # index of Block of two floor
    K0 = num_column_block * num_row_block * 5  # index of Subblock 有H层，因此索引*2
    K1 = num_column_block * num_row_block * 5 * H  # index of Subblock 有H层 K总数
    A0 = num_column_block  # 一层的车道集合
    A= (num_column_block + 1) * 2  # 两层的车道集合，总数=堆场侧列数+1的两倍，考虑了左侧和右侧的车道
    c_ui = np.zeros(V + 2, dtype=int)  # 轮船i的unloading数量
    c_li = np.zeros(V + 2, dtype=int)  # 轮船i的loading数量
    total_i = np.zeros(V + 2, dtype=int)  # 转运到集装箱的数量
    total_s = np.zeros(V + 2, dtype=int)  # 集装箱数量需要总数
    r_ih = np.zeros([V + 2, H], dtype=int)  # 第h层预留的集装箱数量
    l_it = np.zeros([V + 2, T], dtype=int)  # 第t个time step是否有装卸任务
    w_ika = np.zeros([V + 2, K1, A], dtype=int)  # 从subblock k 到轮船 i 的泊位经过lane a 为1，否则为0
    W_a = np.zeros(A, dtype=int)  # 第a个lane在一个time step内能够经过的车辆
    berth_i = np.zeros(V + 2, dtype=int)  # 第i艘船停泊的泊位
    t_lki = np.zeros([K1, V + 2], dtype=float)  # 从第k个subblock 装载到轮船i的位置的单位运输时间
    t_uki = np.zeros([K1, V + 2], dtype=float)  # 从轮船i卸载到第k个subblock 的单位运输时间
    handle_i = np.zeros(V + 2, dtype=int)
    start_i = np.zeros(V + 2, dtype=int)
    total_handle = np.zeros(T, dtype=int)
    berth_location = np.zeros([V + 2, 2], dtype=int)
    lane_location = np.zeros([A, 2], dtype=int)
    subblock_location = np.zeros([K1, 2], dtype=int)
    set_vessel = np.ones(V + 2, dtype=int)  # 初始化所有船的计划集合长度为1，后面更新过程中，动态改变计划集合的长度

def change_seed(seed_index):
    '''
    循环多次跑算例,每次跑算例前，需要把参数中的列表全部清空，重新生成参数
    :param seed_index:
    :return:
    '''
    global seed
    global l_it
    global subblock_info
    global block_set
    global neighbor_set
    global neighbor_pair
    global sorted_unique_pairs
    subblock_info.clear()
    block_set.clear()
    neighbor_set.clear()
    neighbor_pair.clear()
    sorted_unique_pairs.clear()
    seed = seed_index
    rd.seed(seed)
    print(f"para.py 中rd.seed = {seed}")
    l_it.fill(0)

def analysis_time(x1,x2):
    global time_u
    global time_p
    # 转运时间的限制
    time_u = x1
    time_p = x2





